/*
 * "$Id: mxml-search.c 427 2011-01-03 02:03:29Z mike $"
 *
 * Search/navigation functions for Mini-XML, a small XML-like file
 * parsing library.
 *
 * Copyright 2003-2010 by Michael R Sweet.
 *
 * These coded instructions, statements, and computer programs are the
 * property of Michael R Sweet and are protected by Federal copyright
 * law.  Distribution and use rights are outlined in the file "COPYING"
 * which should have been included with this file.  If this file is
 * missing or damaged, see the license at:
 *
 *     http://www.minixml.org/
 *
 * Contents:
 *
 *   mxmlFindElement() - Find the named element.
 *   mxmlFindValue()   - Find a value with the given path.
 *   mxmlWalkNext()    - Walk to the next logical node in the tree.
 *   mxmlWalkPrev()    - Walk to the previous logical node in the tree.
 */

/*
 * Include necessary headers...
 */

#include "config.h"
#include "mxml.h"


/*
 * 'mxmlFindElement()' - Find the named element.
 *
 * The search is constrained by the name, attribute name, and value; any
 * NULL names or values are treated as wildcards, so different kinds of
 * searches can be implemented by looking for all elements of a given name
 * or all elements with a specific attribute. The descend argument determines
 * whether the search descends into child nodes; normally you will use
 * MXML_DESCEND_FIRST for the initial search and MXML_NO_DESCEND to find
 * additional direct descendents of the node. The top node argument
 * constrains the search to a particular node's children.
 */

mxml_node_t* 				/* O - Element node or NULL */ mxmlFindElement(
    mxml_node_t* node,	/* I - Current node */
    mxml_node_t* top,	/* I - Top node */
    const char*  name,	/* I - Element name or NULL for any */
    const char*  attr,	/* I - Attribute name, or NULL for none */
    const char*  value,	/* I - Attribute value, or NULL for any */
    int
    descend)  	/* I - Descend into tree - MXML_DESCEND, MXML_NO_DESCEND, or MXML_DESCEND_FIRST */
{
	const char*	temp;			/* Current attribute value */

	/*
	 * Range check input...
	 */

	if (!node || !top || (!attr && value))
	{
		return (NULL);
	}

	/*
	 * Start with the next node...
	 */
	node = mxmlWalkNext(node, top, descend);

	/*
	 * Loop until we find a matching element...
	 */

	while (node != NULL)
	{
		/*
		 * See if this node matches...
		 */
		if (node->type == MXML_ELEMENT &&
		        node->value.element.name &&
		        (!name || !strcmp(node->value.element.name, name)))
		{
			/*
			 * See if we need to check for an attribute...
			 */
			if (!attr)
			{
				return (node);    /* No attribute search, return it... */
			}

			/*
			 * Check for the attribute...
			 */

			if ((temp = mxmlElementGetAttr(node, attr)) != NULL)
			{
				/*
				 * OK, we have the attribute, does it match?
				*/
				if (!value || !strcmp(value, temp))
				{
					return (node);    /* Yes, return it... */
				}
			}
		}

		/*
		 * No match, move on to the next node...
		 */

		if (descend == MXML_DESCEND)
		{
			node = mxmlWalkNext(node, top, MXML_DESCEND);
		}
		else
		{
			node = node->next;
		}
	}

	return (NULL);
}


/*
 * 'mxmlFindPath()' - Find a node with the given path.
 *
 * The "path" is a slash-separated list of element names. The name "*" is
 * considered a wildcard for one or more levels of elements.  For example,
 * "foo/one/two", "bar/two/one", "*\/one", and so forth.
 *
 * The first child node of the found node is returned if the given node has
 * children and the first child is a value node.
 *
 * @since Mini-XML 2.7@
 */

mxml_node_t* 				/* O - Found node or NULL */ mxmlFindPath(
    mxml_node_t* top,		/* I - Top node */
    const char*  path)  	/* I - Path to element */
{
	mxml_node_t*	node;			/* Current node */
	char		element[256];		/* Current element name */
	const char*	pathsep;		/* Separator in path */
	int		descend;		/* mxmlFindElement option */

	/*
	 * Range check input...
	 */

	if (!top || !path || !*path)
	{
		return (NULL);
	}

	/*
	 * Search each element in the path...
	 */
	node = top;

	while (*path)
	{
		/*
		 * Handle wildcards...
		 */
		if (!strncmp(path, "*/", 2))
		{
			path += 2;
			descend = MXML_DESCEND;
		}
		else
		{
			descend = MXML_DESCEND_FIRST;
		}

		/*
		 * Get the next element in the path...
		 */

		if ((pathsep = strchr(path, '/')) == NULL)
		{
			pathsep = path + strlen(path);
		}

		if (pathsep == path || (pathsep - path) >= sizeof(element))
		{
			return (NULL);
		}

		memcpy(element, path, pathsep - path);
		element[pathsep - path] = '\0';

		if (*pathsep)
		{
			path = pathsep + 1;
		}
		else
		{
			path = pathsep;
		}

		/*
		 * Search for the element...
		 */

		if ((node = mxmlFindElement(node, node, element, NULL, NULL,
		                            descend)) == NULL)
		{
			return (NULL);
		}
	}

	/*
	 * If we get this far, return the node or its first child...
	 */

	if (node->child && node->child->type != MXML_ELEMENT)
	{
		return (node->child);
	}
	else
	{
		return (node);
	}
}


/*
 * 'mxmlWalkNext()' - Walk to the next logical node in the tree.
 *
 * The descend argument controls whether the first child is considered
 * to be the next node. The top node argument constrains the walk to
 * the node's children.
 */

mxml_node_t* 				/* O - Next node or NULL */ mxmlWalkNext(
    mxml_node_t* node,		/* I - Current node */
    mxml_node_t* top,		/* I - Top node */
    int
    descend)  	/* I - Descend into tree - MXML_DESCEND, MXML_NO_DESCEND, or MXML_DESCEND_FIRST */
{
	if (!node)
	{
		return (NULL);
	}
	else if (node->child && descend)
	{
		return (node->child);
	}
	else if (node == top)
	{
		return (NULL);
	}
	else if (node->next)
	{
		return (node->next);
	}
	else if (node->parent && node->parent != top)
	{
		node = node->parent;

		while (!node->next)
			if (node->parent == top || !node->parent)
			{
				return (NULL);
			}
			else
			{
				node = node->parent;
			}

		return (node->next);
	}
	else
	{
		return (NULL);
	}
}


/*
 * 'mxmlWalkPrev()' - Walk to the previous logical node in the tree.
 *
 * The descend argument controls whether the previous node's last child
 * is considered to be the previous node. The top node argument constrains
 * the walk to the node's children.
 */

mxml_node_t* 				/* O - Previous node or NULL */ mxmlWalkPrev(
    mxml_node_t* node,		/* I - Current node */
    mxml_node_t* top,		/* I - Top node */
    int
    descend)  	/* I - Descend into tree - MXML_DESCEND, MXML_NO_DESCEND, or MXML_DESCEND_FIRST */
{
	if (!node || node == top)
	{
		return (NULL);
	}
	else if (node->prev)
	{
		if (node->prev->last_child && descend)
		{
			/*
			 * Find the last child under the previous node...
			 */
			node = node->prev->last_child;

			while (node->last_child)
			{
				node = node->last_child;
			}

			return (node);
		}
		else
		{
			return (node->prev);
		}
	}
	else if (node->parent != top)
	{
		return (node->parent);
	}
	else
	{
		return (NULL);
	}
}


/*
 * End of "$Id: mxml-search.c 427 2011-01-03 02:03:29Z mike $".
 */
